Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SETNX and MSETNX commands can't guarantee atomicity #337

Merged
merged 1 commit into from
Jul 21, 2021

Conversation

ShooterIT
Copy link
Member

No description provided.

@ShooterIT ShooterIT requested a review from git-hulk July 21, 2021 08:25
@ShooterIT ShooterIT requested a review from smartlee July 21, 2021 08:30
@ShooterIT ShooterIT merged commit 02b18c5 into apache:unstable Jul 21, 2021
@ShooterIT ShooterIT deleted the setnx branch July 21, 2021 09:32
ShooterIT added a commit to ShooterIT/kvrocks that referenced this pull request Sep 10, 2021
@ShooterIT ShooterIT mentioned this pull request Sep 10, 2021
@enjoy-binbin
Copy link
Member

enjoy-binbin commented Aug 2, 2023

@git-hulk @ShooterIT Do you guys remember how this atomicity was broken? Is there any example?

seem the change casue this issue:

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> msetnx k v1 k v2
(integer) 1
127.0.0.1:6379> get k
"v2

127.0.0.1:6666> flushall
OK
127.0.0.1:6666> msetnx k v1 k v2
(integer) 0
127.0.0.1:6666> get k
"v1"

Redis allow we overriding the same key but kvrocks will fail in this case

@git-hulk
Copy link
Member

git-hulk commented Aug 2, 2023

Hi @enjoy-binbin

Is there any example?

It should be a bit hard to reproduce, but we did exist since the MSET would write key-value one by one, so it will be partially written the DB if Kvrocks exited in the middle way. The inconsistent behavior you mentioned should be also affected by this implementation.

@enjoy-binbin
Copy link
Member

we already checked whether the key exists before:

  for (StringPair pair : pairs) {
    keys.emplace_back(pair.key);
  }
  if (Exists(keys, &exists).ok() && exists > 0) {
    return rocksdb::Status::OK();
  }

so the exist in for loop looks a bit redundant, in which case we need this exist (the for loop one, i.e. this PR diff)?
and another question is do we want to fix this inconsistency?

@git-hulk
Copy link
Member

git-hulk commented Aug 2, 2023

we already checked whether the key exists before:

  for (StringPair pair : pairs) {
    keys.emplace_back(pair.key);
  }
  if (Exists(keys, &exists).ok() && exists > 0) {
    return rocksdb::Status::OK();
  }

so the exist in for loop looks a bit redundant, in which case we need this exist (the for loop one, i.e. this PR diff)? and another question is do we want to fix this inconsistency?

Yes, we should use MGet and MSet at once instead of the loop writes.

@enjoy-binbin
Copy link
Member

you mean i remove the for (StringPair pair : pairs) { loop, and then just call a MSet?
something like this:

-  std::string ns_key;
-  for (StringPair pair : pairs) {
-    AppendNamespacePrefix(pair.key, &ns_key);
-    LockGuard guard(storage_->GetLockManager(), ns_key);
-    if (Exists({pair.key}, &exists).ok() && exists == 1) {
-      return rocksdb::Status::OK();
-    }
-    std::string bytes;
-    Metadata metadata(kRedisString, false);
-    metadata.expire = expire;
-    metadata.Encode(&bytes);
-    bytes.append(pair.value.data(), pair.value.size());
-    auto batch = storage_->GetWriteBatchBase();
-    WriteBatchLogData log_data(kRedisString);
-    batch->PutLogData(log_data.Encode());
-    batch->Put(metadata_cf_handle_, ns_key, bytes);
-    auto s = storage_->Write(storage_->DefaultWriteOptions(), batch->GetWriteBatch());
-    if (!s.ok()) return s;
-  }
+  rocksdb::Status s = MSet(pairs, ttl);
+  if (!s.ok()) return s;
+

@git-hulk
Copy link
Member

git-hulk commented Aug 2, 2023

Yes, but need to check if any key exists before the MSET. MSETNX now is an exclusive command and it'd be better to lock those keys and then remove the exclusive flag from the command.

@enjoy-binbin
Copy link
Member

MSETNX now is an exclusive command and it'd be better to lock those keys and then remove the exclusive flag from the command.

I am not very familiar with the lock part, can you describe it in detail?
the code now may look like:

rocksdb::Status String::MSetNX(const std::vector<StringPair> &pairs, uint64_t ttl, bool *flag) {
  *flag = false;

  int exists = 0;
  std::vector<Slice> keys;
  keys.reserve(pairs.size());
  for (StringPair pair : pairs) {
    keys.emplace_back(pair.key);
  }
  if (Exists(keys, &exists).ok() && exists > 0) {
    return rocksdb::Status::OK();
  }

  rocksdb::Status s = MSet(pairs, ttl);
  if (!s.ok()) return s;

  *flag = true;
  return rocksdb::Status::OK();
}

do you want me to lock the keys before the Exists call (doing a MultiLockGuard guard?)? This will result in an additional AppendNamespacePrefix call since the lock need to call AppendNamespacePrefix and then in Exists it will also need to call AppendNamespacePrefix.
Note that in MSet, there is another AppendNamespacePrefix call, and Mset will do a LockGuard guard.

@git-hulk
Copy link
Member

git-hulk commented Aug 2, 2023

Yes, need to use MultiLockGuard to lock multi keys, and MSet needs to do a bit of change for supporting the non-lock calls. For calling AppendNamespacePrefix twice, I think it's fine since it is just a little CPU overhead.

git-hulk pushed a commit that referenced this pull request Aug 4, 2023
The changes in #337 causing this issue:
```
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> msetnx k v1 k v2
(integer) 1
127.0.0.1:6379> get k
"v2

127.0.0.1:6666> flushall
OK
127.0.0.1:6666> msetnx k v1 k v2
(integer) 0
127.0.0.1:6666> get k
"v1"
```

Redis allow we overriding the same key but kvrocks will
fail in this case. The way to handle it is to revert the
changes in #337.

So this PR is based on that, after reverting the changes
of #337, we can reuse the logic of MSet. And hulk mentions
that MSETNX is an exclusive command and we better to lock
those keys and then remove the exclusive flag from the command.

So we use MultiLockGuard before Exists call to lock multi
keys. And change MSet to support non-lock calls. And also
remove the exclusive flag from MSETNX command.
p1u3o pushed a commit to p1u3o/incubator-kvrocks that referenced this pull request Aug 15, 2023
The changes in apache#337 causing this issue:
```
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> msetnx k v1 k v2
(integer) 1
127.0.0.1:6379> get k
"v2

127.0.0.1:6666> flushall
OK
127.0.0.1:6666> msetnx k v1 k v2
(integer) 0
127.0.0.1:6666> get k
"v1"
```

Redis allow we overriding the same key but kvrocks will
fail in this case. The way to handle it is to revert the
changes in apache#337.

So this PR is based on that, after reverting the changes
of apache#337, we can reuse the logic of MSet. And hulk mentions
that MSETNX is an exclusive command and we better to lock
those keys and then remove the exclusive flag from the command.

So we use MultiLockGuard before Exists call to lock multi
keys. And change MSet to support non-lock calls. And also
remove the exclusive flag from MSETNX command.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants